跳到主要内容

Go 的管道流 Pipe

管道流 io.Pipe()

学习 HTTP 的时候看到用到了个 io.Pipe() 包,使用如下

// 流式写入
func main() {
r, w := io.Pipe()

go func() {
for i := 0; i < 100; i++ {
w.Write([]byte(fmt.Sprintf("line: %d.", i)))
}
w.Close()
}()

http.Post("localhost:8000", "text/pain", r)
}

io.Pipe() 它返回了一个 Reader 和一个 Writer,它到底能干嘛呢?

流式传输时可以使用到:

随便写个服务端

func main() {
http.HandleFunc("/report", func(rw http.ResponseWriter, r *http.Request) {
// 这个 Fprintf 可以把数据输入到指定的 IO 中
fmt.Fprintf(rw, "hello world")
})

http.ListenAndServe(":8000", nil)
}

重点是这个客户端:

pr, pw := io.Pipe()
// 开协程写入大量数据
go func(){
for i := 0; i < 100; i++ {
w.Write([]byte(fmt.Sprintf("line: %d.", i)))
}
pw.Close()
}()
// 传递Reader
http.Post("http://localhost:8000/report", "text/pain", r)

具体的工作原理看下面

这段转载自 理解golang io.Pipe

其实 io.Pipe() 返回的不仅仅是简单的一个 Writer 一个 Reader ,它返回的是息息相关的一个 Writer 和一个 Reader。

下面我先用比较口语化的方式来讲一下它们是如何工作的。

假设:先假设我们在工地上,有两个工人,一个叫 w,一个叫 r,w 负责搬砖,而 r 负责砌墙。

初始:w 和 r 一起配合工作,一开始啥都没有,负责砌墙的r就没法工作,于是它开始睡觉(Wait)。而w只能去搬砖了。

砖来了:w 深知 r 懒惰的习性,当它把砖搬过来后,就把 r 叫醒(Signal)。然后 w 心想,反正你砌墙也要一会儿,那我也睡会儿。于是 w 叫醒 r 后它也开始睡觉(Wait)。

砌墙:r 被叫醒之后,心想着睡了这么久害怕被包工头责骂,自然就开始辛勤地砌墙了,很快就把 w 搬过来的砖用完了。r 心想,这墙砌不完可怪不到我头上,因为没砖了,于是 r 叫醒了 w,然后自己又去睡觉了。

继续搬砖:w 被叫醒后一看,哎哟我去,这么快就没砖了?然后他又跑去搬了些转过来,然后叫醒睡得跟死猪一样的r起来砌墙,自己又开始睡觉……

周而复始,直到…… w 和 r 两人就这么周而复始地配合,直到 r 发现墙砌好了,或者 w 发现工地上已经没有砖了。

以上大概就是 Pipe 的通俗的解释。不过问题也来了,这俩人瞌睡怎么这么多呢?w 干活 r 就歇着,不能同时干吗?答案是——不能

为什么?因为 Pipe 就是为了某些特定场景而提出的。Pipe 适用于,产生了一条数据,紧接着就要处理掉这条数据的场景。而且因为其内部是一把大锁,因此是线程安全的。(同时因为是阻塞的,所以要开一个协程来操作,否则两个都在一个协程里面会造成死锁)

使用例

TODO: ...